﻿namespace Jsl.FxCop
{
    using System;
    using System.ComponentModel;
    using System.Diagnostics.Contracts;
    using System.Linq;
    using Microsoft.FxCop.Sdk;

    /// <summary>
    /// Identified property and field attributes that are inconsistent.
    /// </summary>
    public class FixInconsistentAttributes : BaseIntrospectionRule
    {
        private static readonly string[] cantHaveIfBrowsableFalse = new string[]
        {
            typeof(CategoryAttribute).FullName,
            typeof(DescriptionAttribute).FullName
        };

        private static readonly string[] cantHaveIfHidden = new string[]
        {
            typeof(DefaultValueAttribute).FullName
        };

        private static readonly string[] cantHaveOnNonPublicOrNonComponent = new string[]
        {
            typeof(BrowsableAttribute).FullName,
            typeof(CategoryAttribute).FullName,
            typeof(DefaultValueAttribute).FullName,
            typeof(DescriptionAttribute).FullName,
            typeof(DesignerSerializationVisibilityAttribute).FullName
        };

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="FixInconsistentAttributes"/> class.
        /// </summary>
        public FixInconsistentAttributes()
            : base(typeof(FixInconsistentAttributes).Name, "Jsl.FxCop.Rules", typeof(FixInconsistentAttributes).Assembly)
        {
        }
        #endregion Constructors

        #region Public Methods
        /// <summary>
        /// Checks the specified member.
        /// </summary>
        /// <param name="member">The member.</param>
        /// <returns>Any problems found.</returns>
        public override ProblemCollection Check(Member member)
        {
            if (member == null)
            {
                throw new ArgumentNullException("member");
            }

            // Only process public fields and properties.
            if ((member.NodeType != NodeType.Field) && (member.NodeType != NodeType.Property))
            {
                return this.Problems;
            }

            // Don't process generated code.
            Contract.Assume(member.DeclaringType != null);
            if (member.DeclaringType.IsGeneratedCode())
            {
                return this.Problems;
            }

            if (member.IsPublic && TypeNodeHelper.IsAssignableTo(member.DeclaringType, typeof(Component)))
            {
                this.ProcessComponentMember(member);
            }
            else
            {
                this.ProcessNonPublicOrNonCompoent(member);
            }

            return this.Problems;
        }
        #endregion Public Methods

        #region Private Methods
        private void ProcessComponentMember(Member member)
        {
            Contract.Assume(member.Attributes != null);

            var browsableFalseAttributes =
                from attribute in member.Attributes
                where attribute.Type.FullName == WellKnownTypeNames.BrowsableAttribute
                    && !(bool)((Literal)attribute.Expressions[0]).Value
                select attribute;

            if (browsableFalseAttributes.Any())
            {
                var inconsitentAttributes =
                    from attribute in member.Attributes
                    where TypeNodeHelper.IsAssignableTo(attribute.Type, FixInconsistentAttributes.cantHaveIfBrowsableFalse)
                    select attribute;

                foreach (AttributeNode attribute in inconsitentAttributes)
                {
                    this.Problems.Add(
                        new Problem(
                            this.GetNamedResolution(
                                "RemoveAttributeWhenBrowsableFalse",
                                attribute.Type.Name.Name,
                                member.Name.Name)));
                }
            }

            var hiddenAttributes =
                from attribute in member.Attributes
                where attribute.Type.FullName == WellKnownTypeNames.DesignerSerializationVisibilityAttribute
                   && (int)((Literal)attribute.Expressions[0]).Value == 0
                select attribute;

            if (hiddenAttributes.Any())
            {
                var inconsitentAttributes =
                    from attribute in member.Attributes
                    where TypeNodeHelper.IsAssignableTo(attribute.Type, FixInconsistentAttributes.cantHaveIfHidden)
                    select attribute;

                foreach (AttributeNode attribute in inconsitentAttributes)
                {
                    this.Problems.Add(
                        new Problem(
                            this.GetNamedResolution(
                                "RemoveAttributeWhenHidden",
                                attribute.Type.Name.Name,
                                member.Name.Name)));
                }
            }
        }

        private void ProcessNonPublicOrNonCompoent(Member member)
        {
            Contract.Assume(member.Attributes != null);

            var inconsitentAttributes =
                from attribute in member.Attributes
                where TypeNodeHelper.IsAssignableTo(attribute.Type, FixInconsistentAttributes.cantHaveOnNonPublicOrNonComponent)
                select attribute;

            foreach (AttributeNode attribute in inconsitentAttributes)
            {
                var resolutionToUse = member.IsPublic ? "RemoveTheAttributeFromNonComponent" : "RemoveAttributeWhenNonPublic";
                this.Problems.Add(
                    new Problem(
                        this.GetNamedResolution(
                            resolutionToUse,
                            attribute.Type.Name.Name,
                            member.Name.Name)));
            }
        }
        #endregion Private Methods
    }
}